import sys
import os
import zipfile
import random
import json
# import scipy.io as io
import numpy as np
from scipy.special import softmax
import paddle
# import matplotlib.pyplot as plt
from paddle.nn import Conv2D, MaxPool2D, Linear
import paddle.nn.functional as F
import paddle.vision.transforms as T
from paddle.static import InputSpec
import pandas as pd
import matplotlib.pyplot as plt


'''
参数配置
'''
train_parameters = {
    "input_size": [8, 10],  # 输入的shape
    "class_dim": 13,  # 分类数
    "src_path": "data/flight_data.zip",  # 原始数据集路径
    "target_path": "work/data/",  # 要解压的路径
    "label_dict": {},  # 标签字典
    "num_epochs": 160,  # 训练轮数
    "train_batch_size": 32,  # 训练时每个批次的大小
    "learning_strategy": {  # 优化函数相关的配置
        "lr": 0.0001  # 超参数学习率
    },
    'skip_steps': 5,  # 每N个批次打印一次结果
    'save_steps': 10,  # 每N个批次保存一次模型参数
    "checkpoints": "work/checkpoint/",  # 保存的路径
    'flight_parameter': 8  # 飞行参数数量，8个数据为1个采样点
}


# 确保目标路径和检查点路径存在
def ensure_dirs_exist(paths):
    for path in paths:
        if not os.path.exists(path):
            os.makedirs(path)


# 需要确保存在的路径列表
paths_to_check = [
    "data",
    train_parameters["target_path"],
    train_parameters["checkpoints"],
    "work/npy"
]

# 检查并创建这些路径
ensure_dirs_exist(paths_to_check)


def unzip_data(src_path, target_path):
    """
    解压原始数据集，将src_path路径下的zip包解压至target_path目录下
    """
    z = zipfile.ZipFile(src_path, 'r')
    z.extractall(path=target_path)  # 将ZIP文件内容解压到指定路径
    z.close()


unzip_data(train_parameters['src_path'], train_parameters['target_path'])  # 解压缩数据集
data = []  #保存数据集
files = os.listdir(train_parameters['target_path'])  # 获取解压目标路径下的所有文件，存储在 files 列表中。
print("解压文件夹内容：", files)
for file in files:
    if file != '.ipynb_checkpoints':
        position = train_parameters["target_path"] + file  # 对于每个文件，拼接其完整路径
        # print(position) # 输出文件路径
        with open(position, 'r') as f:  # 打开文件读取内容
            for line in f.readlines():
                sample = line.split(',')  # 逐行读取文件内容，将每行按逗号分割成列表 sample，然后添加到 data 列表中。
                data.append(sample)
data = np.array(data, dtype=float)  # 将 data 列表转换为 NumPy 数组，并指定数据类型为浮点数
print("13个文件总数据维度：", data.shape)
# print("第一条数据内容：", data[0])


# # 保存融合数据为Excel文件
# df = pd.DataFrame(data)
# excel_path = 'data.xlsx'
# df.to_excel(excel_path, index=False, engine='openpyxl')
# print(f"数据已保存到Excel文件：{excel_path}")


lbl = np.real(data[:, 0])  # 提取数据的标签
flight_parameter = train_parameters['flight_parameter']  # 提取飞行参数数量
# 初始化一个三维数组X，用于存储重构后的数据
# 8个数据为1个采样点
# (data.shape[1] - 1) = 除去标签后的特征数量
# X = (样本数量 23400, 飞行参数数量 8 , 采样点数量 10)
X = np.zeros((data.shape[0], flight_parameter, int((data.shape[1] - 1) / flight_parameter)))

# 重构数据
for ii in range(data.shape[0]):
    # a=data[ii,1:data.shape[1]]
    # 提取特征并重新构造，data[ii, 1:data.shape[1]] 提取当前行的所有特征（除了第一列标签）
    flag = np.transpose(data[ii, 1:data.shape[1]].reshape(-1, flight_parameter))
    X[ii, :, :] = flag
print("重构后标签维度：", lbl.shape)
print("重构后数据维度：", X.shape)

# 归一化 (实际上是中心化（即将数据的均值调整为0）)
for ii in range(X.shape[0]):
    for jj in range(X.shape[1]):
        X[ii, jj, :] = X[ii, jj, :] - np.mean(X[ii, jj, :])  # 每个特征值 减 特征均值
print("归一化后数据维度：(%d, %d, %d)" % X.shape)

# 计算训练集和测试集的样本数量
n_examples = X.shape[0]
n_train = n_examples * 0.9  # 90%的数据作为训练集
train_idx = np.random.choice(range(0, n_examples), size=int(n_train), replace=False)  # 随机选择90%的索引作为训练集
test_idx = list(set(range(0, n_examples)) - set(train_idx))  # 剩余的作为测试集

X_train = X[train_idx]  # 训练集
X_test = X[test_idx]  # 测试集
Y_train = lbl[train_idx]  # 训练集标签
Y_test = lbl[test_idx]  # 测试集标签

np.save('work/npy/X_train.npy', X_train)
np.save('work/npy/X_test.npy', X_test)
np.save('work/npy/Y_train.npy', Y_train)
np.save('work/npy/Y_test.npy', Y_test)

print("训练集维度", X_train.shape)
print("测试集维度", X_test.shape)
print("训练集标签", Y_train)
print("测试集标签", Y_test)


# 定义自定义数据集类
class MR_dataset(paddle.io.Dataset):
    """
    步骤一：继承paddle.io.Dataset类
    """

    def __init__(self, mode):
        """
        步骤二：实现构造函数，定义数据读取方式
        """
        super(MR_dataset, self).__init__()

        self.mode = mode
        self.transforms = T.ToTensor()

        if self.mode == 'train':
            # 加载随机数据
            self.data = X_train
            self.label = Y_train

        elif self.mode == 'test':
            # 加载随机数据
            self.data = X_test
            self.label = Y_test

        # self.transforms = T.ToTensor()   # 数据的格式转换和标准化 HWC => CHW

    def __getitem__(self, index):
        """
        步骤三：实现__getitem__方法，定义指定index时如何获取数据，并返回单条数据（训练数据，对应的标签）
        """
        data = np.array(self.data[index], dtype='float32')
        data = self.transforms(data)

        label = np.array(self.label[index], dtype='int64')

        return data, label

    def __len__(self):
        """
        步骤四：实现__len__方法，返回数据集总数目
        """
        return len(self.data)


train_dataset = MR_dataset(mode='train')
test_dataset = MR_dataset(mode='test')


class MyCNN(paddle.nn.Layer):
    def __init__(self, num_classes=13):
        super(MyCNN, self).__init__()
        # 定义卷积层，输出特征通道out_channels设置为32，卷积核的大小kernel_size为5，卷积步长stride=1，填充padding=2
        self.conv1 = Conv2D(in_channels=1, out_channels=32, kernel_size=5, stride=1, padding=2)
        # 定义池化层，池化核的大小kernel_size为2，池化步长为2
        # self.max_pool1 = MaxPool2D(kernel_size=2, stride=2)
        # 定义卷积层，输出特征通道out_channels设置为16，卷积核的大小kernel_size为5，卷积步长stride=1，填充padding=2
        self.conv2 = Conv2D(in_channels=32, out_channels=16, kernel_size=5, stride=1, padding=2)
        # 定义池化层，池化核的大小kernel_size为2，池化步长为2
        # self.max_pool2 = MaxPool2D(kernel_size=2, stride=2)
        # 定义一层全连接层，输出维度是13
        self.fc = Linear(in_features=1280, out_features=13)

    # 网络的前向计算过程
    def forward(self, input):
        x = self.conv1(input)
        x = F.relu(x)
        #x = self.max_pool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        #x = self.max_pool2(x)
        x = paddle.reshape(x, [x.shape[0], -1])
        x = self.fc(x)

        return x


# 训练
# 高层API
# 定义输入


# shape=[-1, 1, 8, 10]表示输入数据的批次大小是动态的（-1表示任意大小），每个样本有1个通道，高度为8，宽度为10
inputs = InputSpec(shape=[-1, 1, 8, 10], dtype='float32', name='inputs')
labels = InputSpec(shape=[-1], dtype='int64', name='labels')

# 自定义的卷积神经网络模型
myModel = MyCNN()
# 将 MyCNN 封装为一个PaddlePaddle模型，并指定输入和标签的规格
model = paddle.Model(myModel, inputs=inputs, labels=labels)

# 打印模型结构
model.summary()

# 定义了一个Adam优化器，它是一种基于梯度下降的优化算法，用于更新模型的权重。learning_rate是从train_parameters字典中获取的学习率
optimizer = paddle.optimizer.Adam(learning_rate=train_parameters['learning_strategy']['lr'],
                                  parameters=model.parameters())
print("\n")
print("***************模型训练*********************")
# 准备模型训练
model.prepare(optimizer=optimizer,
              loss=paddle.nn.CrossEntropyLoss(),  # 损失函数使用交叉熵
              metrics=paddle.metric.Accuracy())  # 评价指标使用准确率

model.fit(train_data=train_dataset,
          eval_data=test_dataset,
          batch_size=train_parameters['train_batch_size'],
          epochs=train_parameters['num_epochs'],
          save_dir=train_parameters['checkpoints'],
          verbose=1
          )

# 模型评估
print("\n")
print("***************模型评估*********************")
# 实例化模型对象并加载模型参数
model_eval = paddle.Model(MyCNN(), inputs=inputs, labels=labels)
model_eval.load('work/checkpoint/final')
# 为模型使用做准备，设置优化器，损失函数和精度计算方式
model_eval.prepare(paddle.optimizer.Adam(parameters=model_eval.parameters()),
                   paddle.nn.CrossEntropyLoss(),
                   paddle.metric.Accuracy())
# 使用所有测试集进行模型评估
result = model_eval.evaluate(test_dataset, verbose=1)
print("评估结果", result)

# 模型预测
print("\n")
print("***************模型预测*********************")
test_result = model_eval.predict(test_dataset)
# 使用np.array()函数将列表转换为NumPy数组
predict_numpy = np.array(test_result)
# print(predict_numpy.shape) # (1, 2340, 1, 13)
# 获取预测标签，即每个样本预测结果中最大值的索引
predict_labels = np.argmax(predict_numpy, axis=-1)  # axis=-1 表示最后一个维度
# 使用 np.squeeze() 方法去除单维度条目
predict_labels = np.squeeze(predict_labels)
# print(predict_labels)
# print(predict_labels.shape) # (2340,)


actual_labels = np.array(test_dataset.label)
# print(actual_labels)
# print(actual_labels.shape) # (2340,)


# # 按照“实际标签：*，预测标签：*”的格式输出结果
# for actual, predicted in zip(actual_labels, predict_labels):
#     print(f"实际标签：{int(actual)}，预测标签：{predicted}")

# 计算预测正确的样本数量
correct_count = np.sum(actual_labels == predict_labels)
# 计算准确度
accuracy = correct_count / len(actual_labels)
# 打印准确度
print(f"准确度: {accuracy:.4f} , {correct_count}/{len(actual_labels)} ({100 * accuracy:.2f}%)")
